package com.cby.library.base.web;

import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.webkit.ClientCertRequest;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

/**
 * @author ChenBaoyang
 * @description: WebViewClientUtils
 * @date 2021/12/1 09:42
 */
public class WebViewClientUtils {

    /**
     * WebView 加载https遇到证书问题，利用忽略SSL证书错误，继续加载页面
     * @return
     */
    public static WebViewClient getIgnoreHttpsClient() {
        return new WebViewClient(){
            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                // cancel()
                // 停止加载问题页面
                // proceed()
                // 忽略SSL证书错误，继续加载页面
                handler.proceed();
            }
        };
    }

    /**
     * WebView 创建一个验证证书去,来处理https的加载问题
     * @return
     */
    public static WebViewClient getCertificateClient() {
        return new WebViewClient(){
            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                CertificateUtils.onCertificateOfVerification(handler, view.getUrl());
            }
        };
    }

    /**
     * <p>
     * 有种情况是这样的，当webview加载url时，它并不调用onReceivedSslError方法，
     * 而是在onReceivedError方法里返回net::ERR_SSL_PROTOCOL_ERROR。原因呢，
     * 当时我调用是我们的链接，而这个链接则请求的是第三方请求，但是这个第三方请求必
     * 须要验证证书，这个时候如果没有验证证书，第三方请求就会返回一个错误给我们的链接，
     * 然后在返回到客户端。也就是说第三方直接否定了这次请求，这个请求呢，就不是我们
     * 的链接能控制的了。所以这个时候是调用的是onReceivedError，
     * 而不调用onReceivedSslError。只是，最根本的原理是，webview加载html资源，
     * 默认支持的是http请求而不是https请求。所以我们是不是只要把http请求替换成带
     * 验证证书的https请求就好了
     * </p>
     *
     * <p>
     *  在WebViewClient 类中就提供了拦截的方法shouldInterceptRequest
     *  shouldInterceptRequest有两种重载。
     *  从API 11开始引入，API 21弃用
     *  public WebResourceResponse shouldInterceptRequest (WebView view, String url)
     *
     *  从API 21开始引入
     *  public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
     * </p>
     *
     * <h1>https加载http图片显示不出来：</h1>
     * 1、设置getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
     *
     * 2、getSSLPingClient()
     * 使用时是需要调用就可以了
     * webView.setWebViewClient(getSSLPingClient())
     *
     * <span>h5页面中含有post带参请求,上面的两个方法都不适用</span>
     *
     * @return
     */
    public static WebViewClient getSSLPingClient() {
        return new WebViewClient(){
            @Override
            public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
                return WebUtils.processRequest(Uri.parse(url));
            }

            @Override
            @TargetApi(21)
            public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest interceptedRequest) {
                return WebUtils.processRequest(interceptedRequest.getUrl());
            }
        };
    }

    public static WebViewClient getX509CertificateClient(Context context) {
        return new WebX509CertificateClient(context);
    }

    private static class WebX509CertificateClient extends WebViewClient{

        private X509Certificate[] certificatesChain;
        private PrivateKey clientCertPrivateKey;
        public WebX509CertificateClient(Context context) {
            initPrivateKeyAndX509Certificate(context);
        }

        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
            if ((null != clientCertPrivateKey)
                    && ((null != certificatesChain)
                    && (certificatesChain.length != 0))) {
                request.proceed(clientCertPrivateKey, certificatesChain);
            } else {
                request.cancel();
            }
        }


        private static final String KEY_STORE_CLIENT_PATH = "client.p12";//客户端要给服务器端认证的证书
        private static final String KEY_STORE_PASSWORD = "btydbg2018";// 客户端证书密码

        private void initPrivateKeyAndX509Certificate(Context context) {
            try {
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
                keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                Enumeration<?> localEnumeration;
                localEnumeration = keyStore.aliases();
                while (localEnumeration.hasMoreElements()) {
                    String str3 = (String) localEnumeration.nextElement();
                    clientCertPrivateKey = (PrivateKey) keyStore.getKey(str3, KEY_STORE_PASSWORD.toCharArray());
                    if (clientCertPrivateKey == null) {
                        continue;
                    } else {
                        Certificate[] arrayOfCertificate = keyStore.getCertificateChain(str3);
                        certificatesChain = new X509Certificate[arrayOfCertificate.length];
                        for (int j = 0; j < certificatesChain.length; j++) {
                            certificatesChain[j] = ((X509Certificate) arrayOfCertificate[j]);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onReceivedSslError(final WebView view, SslErrorHandler handler,
                                       SslError error) {
            handler.proceed();
        }
    }
}
